#include <Core/Core.hpp>
#include <event2/event.h>
#include <thread>


namespace NetTest
{
	// Helper binder.
	typedef std::function< void ( evutil_socket_t, short ) >	EventCallback_t;
	void	callbackBinder( evutil_socket_t s, short what, void* userData )
	{
		EventCallback_t&	cb	=	*(EventCallback_t*)userData;
		cb( s, what );
	}


	inline void	SenderThread()
	{
		LOG( INFO ) << "Thread running.";

		// Create a new event base.
		event_base*	eventBase	=	event_base_new();

		//
		evutil_socket_t	udpSocket	=	socket( AF_INET, SOCK_DGRAM, 0 );

		// Create an event for the socket write.
		EventCallback_t	cb	=	[&]( evutil_socket_t fd, short what )
		{
			// Create the target address.
			sockaddr_in	destAddr;
			memset( &destAddr, 0, sizeof( sockaddr_in ) );
			destAddr.sin_family			=	AF_INET;
			destAddr.sin_addr.s_addr	=	inet_addr( "127.0.0.1" );
			destAddr.sin_port			=	htons( 25000 );

			if( what&EV_TIMEOUT )
			{
				LOG( INFO ) << "Timeout.";
				event_base_loopbreak( eventBase );
			}
			else
			{
				LOG( INFO ) << "Sending.";
				uint32_t	count	=	1;
				sendto( udpSocket, (const char*)&count, sizeof( count ), 0, (const sockaddr *)&destAddr, sizeof( destAddr ) );
			}
		};

		event*	udpWrite	=	event_new( eventBase, udpSocket, EV_WRITE | EV_TIMEOUT, callbackBinder, &cb );

		// Create a timeout and add the event.
		timeval		udpTimeout	=	{1, 0};
		event_add( udpWrite, &udpTimeout );

		// Just loop the event base.
		event_base_dispatch( eventBase );

		// Close the socket.
		evutil_closesocket( udpSocket );

		// Free the event.
		event_del( udpWrite );
		event_free( udpWrite );

		// Free the event base.
		event_base_free( eventBase );
	}
}


TEST( Net, GeneralTest )
{
	// Initialize windows sockets bs.
#ifdef WIN32
	WSADATA		WSAData;
	WSAStartup(	0x101, &WSAData	);
#endif

	// Create a new event base.
	event_base*	eventBase	=	event_base_new();

	// Setup the local udp address+port.
	sockaddr_in		udpAddress;
	memset( &udpAddress, 0, sizeof( sockaddr_in ) );
	udpAddress.sin_family	=	AF_INET;
	udpAddress.sin_addr.s_addr	=	htonl( INADDR_ANY );
	udpAddress.sin_port			=	htons( 25000 );

	// Create and bind the socket.
	evutil_socket_t	udpSocket	=	socket( AF_INET, SOCK_DGRAM, 0 );
	bind( udpSocket, (sockaddr*)&udpAddress, sizeof( udpAddress ) );

	// Create an event for the socket read.
	event*	udpRead	=	event_new( eventBase, udpSocket, EV_READ | EV_PERSIST | EV_TIMEOUT,
		[]( evutil_socket_t fd, short what, void *arg ) -> void
		{
			if( what&EV_TIMEOUT )
			{
				LOG( INFO ) << "Timeout.";
				event_base_loopbreak( (event_base*)arg );
			}
			else
			{
				uint32_t			count;
				sockaddr_storage	fromAddr;
				int					fromAddrLen	=	sizeof( fromAddr );
				int					readCount	=	recvfrom( fd, (char *)&count, sizeof( count ), 0, (sockaddr *)&fromAddr, &fromAddrLen );
				LOG( INFO ) << "Read data " << readCount << ".";
			}
		},
		eventBase );

	// Create a timeout and add the event.
	timeval		udpTimeout	=	{1, 0};
	event_add( udpRead, &udpTimeout );

	//////////////////////////////////////////////////////////////////////////
	// Start the worker thread.
	std::thread		worker( NetTest::SenderThread );

	// Just loop the event base.
	event_base_dispatch( eventBase );

	//////////////////////////////////////////////////////////////////////////
	// Join the worker thread.
	worker.join();

	// Close the socket.
	evutil_closesocket( udpSocket );

	// Free the event.
	event_del( udpRead );
	event_free( udpRead );

	// Free the event base.
	event_base_free( eventBase );

	// Cleanup windows sockets bs.
#ifdef WIN32
	WSACleanup();
#endif
}
